Lists
In this chapter, we will start with our crypto coin portal example. We will begin with a basic GraphQL server that exposes an Asset
and an AssetPrice
entity to allow the GraphQL portal to fetch the available crypto assets and price information.
During this chapter, we will cover the following topics:
- Exposing lists of entities from a database
- Cursor pagination
- Global Object Identification
- DataLoader
Preparations
Before we can begin, we need to clone the workshop repository. To do this, open a terminal on your system. Go to a location on your system where you want to store the repository.
git clone https://github.com/ChilliCream/workshops.git
Next, open the backend playground directory for this example.
code workshops/crypto/backend/playground/example2
Our example server is a relatively empty project with GraphQL not being correctly configured. We essentially have some basics setup.
A directory called Helper holds some services out of scope for this exercise. It is essentially all the stuff that we need to make this exercise work, but you do not need to care about it. One of the helpers we have put in here will seed your local database with data.
Further, we have a directory Data containing an Entity Framework DbContext
with two entities called Asset
and AssetPrice
.
Asset
contains the base information about a crypto asset, like the name or the icon of a crypto coin.
AssetPrice
contains price information like the latest price or the 24-hour high point.
Asset List
First, we want to expose through GraphQL a simple list of assets. We will add a new Query
class with a resolver to fetch the asset list.
The Query
class will represent the GraphQL root type for query operations. Query operations in GraphQL represent side-effect-free read operations.
Open your terminal within Visual Studio Code and create a new directory Types
. The Types
directory will be home to our GraphQL types.
mkdir Types
Next, create the Query.cs
file and add it to the Types
directory. Copy the following code into the new file.
namespace Demo.Types;
[QueryType]
public static class Query
{
public static IQueryable<Asset> GetAssets(AssetContext context)
=> context.Assets.OrderBy(t => t.Symbol);
}
The GetAssets
method within the Query
class represents a resolver for the GraphQL root field assets
on the Query
type.
type Query {
assets: [Asset!]!
}
We applied GraphQL naming conventions to the method name GetAssets
which removed the Get
verb from the name and applied camelCase to it.
To fetch the assets from our database, we need an AssetContext
. With Hot Chocolate, we can ask for services required in our resolver by adding them as parameters. We call this resolver-level dependency injection.
In GraphQL, it has benefits to use resolver-level dependency injection since we only will create resources for services we need. Further, the execution engine can resolve those service dependencies for us, allowing it to consider service characteristics like pooled resources or single-threaded services.
In this example, the execution engine will rent a pooled DbContext
and return it after the resolver completes its work.
By default, the execution engine will try to execute resolvers in parallel when executing queries. See: https://spec.graphql.org/October2021/#sec-Normal-and-Serial-Execution
Configuration
Now that we have added our Query
root type, we need to register it with our GraphQL configuration. Head over to the Program.cs
where all our service configuration is located.
Hot Chocolate comes with a source generator that generates GraphQL configuration code for us. Now that a GraphQL type exists it has created an extension method called AddTypes
that we can use in our GraphQL configuration.
builder.Services
.AddGraphQLServer()
.AddTypes();
The completed Program.cs
should look like the following after you have added AddTypes()
.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddHttpContextAccessor()
.AddCors()
.AddHelperServices();
builder.Services
.AddDbContextPool<AssetContext>(o => o.UseSqlite("Data Source=assets.db"));
builder.Services
.AddGraphQLServer()
.AddTypes();
var app = builder.Build();
app.UseCors(c => c.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseStaticFiles();
app.MapGraphQL();
app.Run();
The HotChocolate source generator will automatically add all types that are annotated with one of the following attributes the the schema module.
- ExtendObjectTypeAttribute
- ObjectTypeAttribute
- InterfaceTypeAttribute
- UnionTypeAttribute
- EnumTypeAttribute
- InputObjectTypeAttribute
Also, all classes extending the following classes will be automatically added.
- ObjectType
- InterfaceType
- UnionType
- EnumType
- InputObjectType
- ScalarType
- ObjectTypeExtension
- InterfaceTypeExtension
- UnionTypeExtension
- InputObjectTypeExtension
- EnumTypeExtension
Also all DataLoader will be automatically added to the GraphQL module.
- IDataLoader
The GraphQL module name is defined in the ModuleInfo.cs
file.
[assembly: Module("Types")]
Last but not least, we need to register the AssetContext
with the GraphQL configuration so that the execution engine understands that AssetContext
is a service and how to handle this service. For this chain in the following line with the GraphQL configuration.
builder.Services
.AddGraphQLServer()
.AddTypes()
.RegisterDbContext<AssetContext>();
The Program.cs
should now look like the following.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddHttpContextAccessor()
.AddCors()
.AddHelperServices();
builder.Services
.AddDbContextPool<AssetContext>(o => o.UseSqlite("Data Source=assets.db"));
builder.Services
.AddGraphQLServer()
.AddTypes()
.RegisterDbContext<AssetContext>();
var app = builder.Build();
app.UseCors(c => c.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseStaticFiles();
app.MapGraphQL();
app.Run();
With version 13 we set the default of RegisterService
and RegisterDbContext
to ServiceKind.Resolver
. This essentially will lead the resolver to create a service scope and retrieve the registered services from this resolver bound service scope.
Before we can move ahead, let us test our new resolver.
dotnet run
Open http://localhost:5000/graphql
. Reload the schema to ensure that Banana Cake Pop has the latest version of our schema in memory.
Now, execute the following GraphQL query.
query GetAllAssets {
assets {
name
}
}
Summary
In the first part, we have learned how to expose a list of database entities through GraphQL. We also explored resolver dependency injection and how we can register the DbContext
with the execution engine.